package mbp.core;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;

import mbp.base.EnumType;
import mbp.base.FeatureType;
import mbp.base.ResourceType;
import mbp.exceptions.InvalidEnumTypeException;
import mbp.exceptions.InvalidFeatureNameException;
import mbp.exceptions.InvalidResourceTypeException;
import mbp.exceptions.ParseExpressionErrorException;

import de.congrace.exp4j.Calculable;
import de.congrace.exp4j.ExpressionBuilder;
import de.congrace.exp4j.UnknownFunctionException;
import de.congrace.exp4j.UnparsableExpressionException;

/**
 * Klasa przeznaczona do wartosciowania zasobu na podstawie wartosci jego cech
 * @author Wojciech Wilanski
 * 
 */
public class ResourceUtility 
{
	private Resource resource;
	private ResourceType resourceType;
	private String resourceTypeName;
	private String expression;
	
	//Kolekcja tablic odwzorowujacych nazwy elementow typu ENUM na wartosci liczbowe int
	private HashMap<String, HashMap<String, Integer>> enumValues;
	
	Logger logger = Logger.getLogger(this.getClass());
	
	/**
	 * Konstruktor - Inicjalizacja obiektu na podstawie gotowego zasobu. 
	 * @param resource - Obiekt zasob: Resource
	 * @see mbp.core.Resource
	 */
	/*public ResourceUtility(Resource resource)
	{
		this.resource = resource;
		this.resourceTypeName = resource.resourceType.GetResourceTypeName();
		enumValues = new HashMap<String, HashMap<String,Integer>>();
		
		Set<String> names = this.resource.resourceType.GetFeaturesCollection().keySet();
		StringBuilder sb = new StringBuilder();
		Iterator it = names.iterator();
		while(it.hasNext())
		{
			
			 * Utworzenie domyslnej regoly matamatycznej.
			 * Sumowanie wszystkich wartosci cech zasobow.
			 
			String key = (String)it.next();
			sb.append(key);			
			if(it.hasNext())sb.append(" + ");
			
			
			 * Utworzenie tablic odwzorowujacych wartosc enum na int i nadanie domy�lnych warto�ci
			 
			FeatureType type = (FeatureType)this.resource.resourceType.GetFeaturesCollection().get(key);			
			if(type == FeatureType.ENUM)
			{
				HashMap<String,Integer> hm = new HashMap<String, Integer>();
				
				String[] enumTypeNames = resource.resourceType.GetEnumTypes().get(key).getArray();
					
				
				for(int i = 0; i<enumTypeNames.length; i++)
				{
					hm.put(enumTypeNames[i], i+1);
				}
				
				
				 * Dodanie tablicy.
				 * key - Nazwa cechy enum
				 * hm  - Tablica HashMap<nazwy_typu_wyliczeniowego, odwzorowanie_int> 
				 
				enumValues.put(key, hm);
			}
			
		}
		this.expression = sb.toString();
	}
*/
	
	/**
	 * Konstruktor - Inicjalizacja obiektu na podstawie typu zasobu
	 * @param resourceType - Obiekt typ zasobu ResourceType
	 * @see mbp.base.ResourceType
	 */
	public ResourceUtility(ResourceType resourceType)
	{
		this.resourceType = resourceType;
		this.resource = null;
		this.resourceTypeName = resourceType.getResourceTypeName();
		enumValues = new HashMap<String, HashMap<String,Integer>>();
		
		Set<String> names = resourceType.getFeaturesCollection().keySet();
		StringBuilder sb = new StringBuilder();
		Iterator it = names.iterator();
		
		while(it.hasNext())
		{
			/*
			 * Utworzenie domyslnej regoly matamatycznej.
			 * Sumowanie wszystkich wartosci cech zasobow.
			 */
			String key = (String)it.next();
			sb.append(key);			
			if(it.hasNext())
			{
				sb.append(" + ");
			}
			else
			{
				sb.append(" + QUANTITY + DATE");
			}
			
			/*
			 * Utworzenie tablic odwzorowujacych wartosc enum na int
			 */
			FeatureType type = resourceType.getFeaturesCollection().get(key);
			if(type == FeatureType.ENUM)
			{
				HashMap<String,Integer> hm = new HashMap<String, Integer>();
				
				String[] enumTypeNames = resourceType.getEnumTypes().get(key).getArray();
					
				for(int i = 0; i<enumTypeNames.length; i++)
				{
					hm.put(enumTypeNames[i], i+1);
				}
				
				/*
				 * Dodanie tablicy.
				 * key - Nazwa cechy enum
				 * hm  - Tablica HashMap<nazwy typu wyliczeniowego, odwzorowanie int> 
				 */
				enumValues.put(key, hm);
			}		
			
		}
		
		this.expression = sb.toString();
		
	}
	
	public ResourceType getType()
	{
		return this.resourceType;
	}
	
	/*
	public void PrintEnumValues()
	{
		Iterator it = enumValues.keySet().iterator();
		while(it.hasNext())
		{
			String key = (String)it.next();
			System.out.println("Feature Name: " + key);
			
			Iterator it2 = enumValues.get(key).keySet().iterator();
			while(it2.hasNext())
			{
				Enum<?> en = (Enum<?>)it2.next();
				System.out.println("  " + en + " | " + enumValues.get(key).get(en));
			}
		}
	}
	*/
	
	/**
	 * Przypisanie warto�ci typu int do podanego elementu typu ENUM (EnumType)
	 * @param featureName - Nazwa cechy
	 * @param enumValue - Nazwa jednego z elementu typu EnumType kt�ry jest przypisany do tej cechy
	 * @param value - Warto�� przypisania
	 * @see mbp.base.EnumType
	 * @throws InvalidFeatureNameException
	 * @throws InvalidEnumTypeException
	 */
	public void setEnumValue(String featureName, String enumValue, Integer value) throws InvalidFeatureNameException, InvalidEnumTypeException
	{
		if(enumValues.containsKey(featureName))
		{
			if(enumValues.get(featureName).containsKey(enumValue))
			{
				enumValues.get(featureName).put(enumValue, value);
			}
			else
			{
				throw new InvalidEnumTypeException();
			}
		}
		else
		{
			throw new InvalidFeatureNameException();
		}
	}
	
	/**
	 * Zwraca nazwe typu zasobu
	 * @return
	 */
	public String getResourceTypeName()
	{
		return this.resourceTypeName;
	}
    
	/**
	 * Zwraca matymatyczna formule wg. ktorej obliczane jest wartosciowanie
	 * @return
	 */
	public String getExrpession() 
	{
		return expression;
	}
	
    /**
     * Zapisuje formule matemetyczna do obliczania uzytecznosci (warto�ciowania) zasobu 
     * @param expression - np: "Rodzaj + Cena * 2 - (Cena * Jakosc)".
     * Gdzie (Rodzaj,Cena,Jakosc) to nazwy cech zasobow.
     */
	public void setExpression(String expression) 
	{
		this.expression = expression;
	}
	
	/**
	 * Oblicza uzytecznosc zasobu na podstawie zdefiniowanej formuly matamatycznej.
	 * Wynik zwraca jako liczbe typu float.
	 * @return
	 * @throws InvalidResourceTypeException 
	 */
	public float calculateUtility(Resource resource) throws InvalidResourceTypeException
	{
		//System.out.println("this.resourceTypeName: " + this.resourceTypeName);
		//System.out.println("resource.resourceType.GetResourceTypeName(): " + resource.GetType().GetResourceTypeName());
		if(this.resourceTypeName.compareTo(resource.getType().getResourceTypeName()) != 0)
		{
			throw new InvalidResourceTypeException();
		}
		
		this.resource = resource;
		
		//System.out.println("expression  : " + expression);
		logger.debug("expression  : " + expression);
		Calculable calc;
		float result = (float)0.0;
		try {
			String exprFormated = prepareExpression(this.expression);
			//System.out.println("exprFormated: " + exprFormated);
			logger.debug("exprFormated: " + exprFormated);
			
			calc = (Calculable) new ExpressionBuilder(exprFormated).build();
			result = (float)calc.calculate();
			//System.out.println("result      : " + result);
			logger.debug("result      : " + result);
		} catch (ParseExpressionErrorException e) {
			logger.error("ParseExpressionErrorException", e);
		} catch (InvalidFeatureNameException e) {
			logger.error("InvalidFeatureNameException", e);
		} catch (UnknownFunctionException e) {
			logger.error("UnknownFunctionException", e);
		} catch (UnparsableExpressionException e) {
			logger.error("UnparsableExpressionException", e);
		}
		return result;
	}
	
	/**
	 * Podstawienie wartosci liczbowych w miejsce nazw cech zasobu
	 * @param exrpession
	 * @return
	 * @throws ParseExpressionErrorException
	 * @throws InvalidFeatureNameException
	 */
	private String prepareExpression(String exrpession) throws ParseExpressionErrorException, InvalidFeatureNameException
	{
		String exprFormatd = expression;
		Iterator it = this.resourceType.getFeaturesCollection().keySet().iterator();
		while(it.hasNext())
		{
			String key = (String) it.next();
			FeatureType type = resource.getFeatureType(key);
			switch(type)
			{
			case INT:
				Integer vInt = (Integer)resource.getFeatureValue(key);
				exprFormatd = exprFormatd.replaceAll(key, vInt.toString());
				break;
			case FLOAT:
				Float vFloat = (Float)resource.getFeatureValue(key);
				exprFormatd = exprFormatd.replaceAll(key, vFloat.toString());
				break;
			case ENUM:
				String vEnum = ((EnumType)resource.getFeatureValue(key)).toString();
				Integer vEnumInt = enumValues.get(key).get(vEnum);
				exprFormatd = exprFormatd.replaceAll(key, vEnumInt.toString());
				break;
			}
		}
		
		exprFormatd = exprFormatd.replaceAll("QUANTITY", resource.getQuantity().toString());
		
		//Obliczenie waznosci zasobu - ilosc sekund do konca waznosci zasobu
		Date dtResource = resource.getValidDate();
		if(dtResource != null)
		{
			Date dtNow = new Date();
			Long diff = ( dtResource.getTime() - dtNow.getTime() ) / 1000;
			exprFormatd = exprFormatd.replaceAll("DATE", diff.toString());
		}
		else
		{
			exprFormatd = exprFormatd.replaceAll("DATE", "0");
		}
		
		//System.out.println("exprFormatd: " + exprFormatd);
		return exprFormatd;
	}
	
	public Set<String> getTableNames()
	{
		return enumValues.keySet();
	}
	
	public HashMap<String, Integer> getTableValues(String tableName)
	{
		return enumValues.get(tableName);
	}
	
}
